package com.hup.utils.swing.frame;

import com.hup.utils.commons.hardware.MouseUtil;
import com.hup.utils.commons.hardware.ScreenUtil;
import com.hup.utils.commons.start.LookThemeUtil;
import com.hup.utils.swing.ComponentUtil;
import com.hup.utils.swing.FrameUtil;
import com.hup.utils.swing.customView.ImageButton;
import lombok.extern.log4j.Log4j2;

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

/**
 * 用鼠标调节组件[位置,大小]的控制器,功能:
 * .移动时,边缘磁吸效果
 * .移动时,限制是否在屏幕内
 * .八方向调节大小
 *
 * @author hugan
 * @date 2020/10/30
 * @apiNote 只支持无边框窗口
 */
@Log4j2
public class WindowBoundsAdjustor extends MouseAdapter implements AWTEventListener {

    public static WindowBoundsAdjustor bind(WindowBoundsAdjustorCallback callback) {
        return new WindowBoundsAdjustor(callback);
    }

    private final Window window;
    private final WindowBoundsAdjustorCallback callback;
    private final boolean inScreen;
    private final int screenEdge;
    private final int resizeEdge;

    /**
     * 按下左键时的状态:鼠标坐标,窗口坐标...
     */
    private int mX0, mY0;
    private int wX0, wY0;
    /**
     * 按下左键时,能否拖拽窗口的横竖轴;用于resize
     */
    private boolean canResizeX, canResizeY;
    /**
     * 拖拽时,窗口横竖轴固定的点坐标;
     * 这是resize代码逻辑的关键,难理解,意会;
     * 另一种代码写法:不使用这种变量,代码可以更容易理解,但代码中的if会增多
     */
    private int wXFixedPoint, wYFixedPoint;
    private boolean canMove;
    private boolean canResize;
    private Insets screenCoor;
    /**
     * 按下左键时的状态
     */
    private boolean hadDragged;//用于过滤,只点击鼠标,无拖拽的事件
    private int releaseX, releaseY, releaseW, releaseH;
    private Boolean releaseOnTop, releaseOnLeft;

    private WindowBoundsAdjustor(WindowBoundsAdjustorCallback callback) {
        this.window = callback.getWindow();
        if (!FrameUtil.isUndecorated(window)) throw new IllegalArgumentException("只支持无边框窗口");
        this.callback = callback;
        inScreen = callback.inScreen();
        screenEdge = callback.screenEdge();
        resizeEdge = callback.resizeEdge();
        window.getToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }

    @Override
    public void eventDispatched(AWTEvent e) {
        if (!(e.getSource() instanceof Component)) return;//过滤托盘图标

        //过滤事件类型
        int id = e.getID();
        boolean matchType = false;
        if (id == MouseEvent.MOUSE_PRESSED
                || id == MouseEvent.MOUSE_RELEASED
                || id == MouseEvent.MOUSE_MOVED
                || id == MouseEvent.MOUSE_DRAGGED) {
            matchType = true;
        }
        if (!matchType) return;
        //过滤其他窗口
        if (window != e.getSource() //source也可能是窗口本身
                && window != ComponentUtil.getParentWindow((Component) e.getSource())) {
            //log.info("otherWin={}", e.paramString());
            return;
        }
        //log.info("e={}", e);
        //本窗口内的事件
        //过滤控件类型
        if (!callback.canDrag(e)) {
            //移动到不可控的组件,需要还原光标
            if (window.getCursor().getType() != Cursor.DEFAULT_CURSOR) {
                window.setCursor(Cursor.getDefaultCursor());
            }
            return;
        }

        MouseEvent me = (MouseEvent) e;
        if (id == MouseEvent.MOUSE_PRESSED) {
            mousePressed(me);
        } else if (id == MouseEvent.MOUSE_MOVED) {
            mouseMoved(me);
        } else if (id == MouseEvent.MOUSE_DRAGGED) {
            mouseDragged(me);
        } else {
            //if (id == MouseEvent.MOUSE_RELEASED) {
            mouseReleased(me);
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        if (!MouseUtil.isLeftButton(e)) return;
        //log.info("");
        mX0 = e.getXOnScreen();
        mY0 = e.getYOnScreen();
        wX0 = window.getX();
        wY0 = window.getY();
        canMove = canResize = canResizeX = canResizeY = hadDragged = false;
        int lastCursor = MouseUtil.updateResizeCursor(mX0, mY0, window, window.getBounds(), resizeEdge);
        if (lastCursor == Cursor.DEFAULT_CURSOR) {
            canMove = true;
        } else if (lastCursor >= Cursor.SW_RESIZE_CURSOR && lastCursor <= Cursor.E_RESIZE_CURSOR) {
            wXFixedPoint = wX0;
            wYFixedPoint = wY0;
            canResize = true;
            if (lastCursor <= Cursor.NE_RESIZE_CURSOR) {
                canResizeX = canResizeY = true;
            } else if (lastCursor <= Cursor.S_RESIZE_CURSOR) {
                canResizeY = true;
            } else {
                canResizeX = true;
            }
            if (lastCursor == Cursor.W_RESIZE_CURSOR || lastCursor == Cursor.NW_RESIZE_CURSOR || lastCursor == Cursor.SW_RESIZE_CURSOR) {
                wXFixedPoint = wX0 + window.getWidth() - 1;//从左侧拖拽,则固定点在当前窗口的右边
            }
            if (lastCursor == Cursor.N_RESIZE_CURSOR || lastCursor == Cursor.NW_RESIZE_CURSOR || lastCursor == Cursor.NE_RESIZE_CURSOR) {
                wYFixedPoint = wY0 + window.getHeight() - 1;//从上方拖拽,则固定点在当前窗口的下方
            }
        }
        log.debug("canMove={}, canResize={}, clz={}", canMove, canResize, e.getSource().getClass());
        if (canMove || canResize) {
            screenCoor = ScreenUtil.getScreenUsableCoor();
            log.debug("screenCoor={}", screenCoor);
            e.consume();//阻止按下事件传递到子组件
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        /*
        只能更新window的光标,不能修改子控件的光标!
        否则会有效果异常,如:释放按钮时,按钮光标是错误的;
        且需要窗口边缘都留有空隙,否则在边缘移动鼠标时,也会有光标更新停滞的问题
         */
        MouseUtil.updateResizeCursor(e.getXOnScreen(), e.getYOnScreen(), window, window.getBounds(), resizeEdge);
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        //log.info("");
        if (canMove) {
            hadDragged = true;
            moveWindow(e);
        } else if (canResize) {
            hadDragged = true;
            resizeWindow(e);
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        //log.info("");
        if (!MouseUtil.isLeftButton(e)) return;
        if (canMove) {
            if (hadDragged) {
                moveWindow(e);
                callback.afterMoved(releaseX, releaseY, releaseOnTop, releaseOnLeft);
                hadDragged = false;
                e.consume();
            }
            canMove = false;
            //阻止按下事件传递到子组件,必须与pressed.consume成对使用,否则按下效果不能被移除
        } else if (canResize) {
            if (hadDragged) {
                resizeWindow(e);
                callback.afterResized(releaseX, releaseY, releaseW, releaseH);
                hadDragged = false;
                e.consume();
            }
            canResize = false;
        }
    }

    private void moveWindow(MouseEvent e) {
        int mX2 = e.getXOnScreen();
        int mY2 = e.getYOnScreen();
        //这里的坐标都是index,起始于0,最大值为[length-1]
        int x = wX0 + (mX2 - mX0);
        int y = wY0 + (mY2 - mY0);
        int rightX = x + window.getWidth() - 1;
        int rightY = y + window.getHeight() - 1;
        Boolean onTop = null, onLeft = null;
        //锁定在屏幕内
        if (inScreen) {
            if (x < 0) x = 0;
            else if (rightX > screenCoor.right) {
                x = screenCoor.right - window.getWidth() + 1;
                rightX = x + window.getWidth() - 1;
            }
            if (y < 0) y = 0;
            else if (rightY > screenCoor.bottom) {
                y = screenCoor.bottom - window.getHeight() + 1;
                rightY = y + window.getHeight() - 1;
            }
        }

        //磁吸效果
        if (x >= 0 && x <= screenEdge) {
            x = 0;
            onLeft = true;
        } else {
            if (rightX >= screenCoor.right - screenEdge && rightX <= screenCoor.right) {
                x = screenCoor.right - window.getWidth() + 1;
                onLeft = false;
            }
        }
        if (y >= 0 && y <= screenEdge) {
            y = 0;
            onTop = true;
        } else {
            if (rightY >= screenCoor.bottom - screenEdge && rightY <= screenCoor.bottom) {
                y = screenCoor.bottom - window.getHeight() + 1;
                onTop = false;
            }
        }

        window.setLocation(x, y);
        releaseX = x;
        releaseY = y;
        releaseOnTop = onTop;
        releaseOnLeft = onLeft;
        //log.info("[{},{}],{},{}", x, y, onTop, onLeft);//test
    }

    private void resizeWindow(MouseEvent e) {
        int x = window.getX();
        int y = window.getY();
        int width = window.getWidth();
        int height = window.getHeight();
        Dimension minSize = callback.getMinimumSize();
        if (callback.canReverseSize()) {
            //能翻转
            if (canResizeX) {
                width = Math.abs(wXFixedPoint - e.getXOnScreen()) + 1;
                if (minSize != null && width < minSize.width) {
                    width = minSize.width;
                    x = e.getXOnScreen() < wXFixedPoint ? wXFixedPoint - width : wXFixedPoint;
                } else {
                    x = Math.min(e.getXOnScreen(), wXFixedPoint);
                }
            }
            if (canResizeY) {
                height = Math.abs(wYFixedPoint - e.getYOnScreen()) + 1;
                if (minSize != null && height < minSize.height) {
                    height = minSize.height;
                    y = e.getYOnScreen() < wYFixedPoint ? wYFixedPoint - height : wYFixedPoint;
                } else {
                    y = Math.min(e.getYOnScreen(), wYFixedPoint);
                }
            }
        } else {
            //不能翻转
            if (canResizeX) {
                if (mX0 > wXFixedPoint) {
                    //拖拽右侧边框
                    width = e.getXOnScreen() - wXFixedPoint + 1;
                    if (minSize != null && width < minSize.width) width = minSize.width;
                } else {
                    //拖拽左侧边框
                    width = wXFixedPoint - e.getXOnScreen() + 1;
                    if (minSize != null && width < minSize.width) {
                        width = minSize.width;
                        x = wXFixedPoint - width;
                    } else {
                        x = e.getXOnScreen();
                    }
                }
            }
            if (canResizeY) {
                if (mY0 > wYFixedPoint) {
                    //拖拽下侧边框
                    height = e.getYOnScreen() - wYFixedPoint + 1;
                    if (minSize != null && height < minSize.height) height = minSize.height;
                } else {
                    //拖拽上侧边框
                    height = wYFixedPoint - e.getYOnScreen() + 1;
                    if (minSize != null && height < minSize.height) {
                        height = minSize.height;
                        y = wYFixedPoint - height;
                    } else {
                        y = e.getYOnScreen();
                    }
                }
            }
        }
        //log.info("[{},{}],[{},{}]", x, y, width, height);
        if (width < 10) width = 10;
        if (height < 10) height = 10;
        window.setBounds(x, y, width, height);
        releaseX = x;
        releaseY = y;
        releaseW = width;
        releaseH = height;
    }

    public interface WindowBoundsAdjustorCallback {

        /**
         * 移动时,是否限制窗口在屏幕内
         *
         * @apiNote 只在绑定监听时赋值一次
         */
        default boolean inScreen() { return true; }

        /**
         * 移动时,边缘磁吸距离
         */
        default int screenEdge() { return (int) (8 * LookThemeUtil.getScreenScale()); }

        /**
         * 调节大小时的边缘距离
         */
        default int resizeEdge() { return (int) (5 * LookThemeUtil.getScreenScale()); }

        /**
         * 通常情况下需要按需重写此方法;
         *
         * @return 能否拖拽鼠标当前所在的组件
         */
        @SuppressWarnings("RedundantIfStatement")
        default boolean canDrag(AWTEvent e) {
            Component c = (Component) e.getSource();
            if (!c.isEnabled()) return true;
            if (c instanceof JScrollBar
                    || c instanceof AbstractButton
                    || c instanceof ImageButton
                    || c instanceof JTextComponent
            ) {
                return false;
            }
            return true;
        }

        /**
         * @return resize时的最小尺寸
         */
        default Dimension getMinimumSize() {return getWindow().getMinimumSize();}

        /**
         * @return resize时能否翻转窗口;默认-无最小尺寸时可以翻转
         */
        default boolean canReverseSize() { return getMinimumSize() == null; }

        /**
         * @return 目标窗口, 需要无边框的;并且边缘留有适当空白,用于resize,否则会出现光标效果异常!!!
         */
        Window getWindow();

        /**
         * @param x 移动后的位置
         * @param onTop true/false-在顶部/底部,null-没有在边缘;与 {@link #screenEdge()}相关
         */
        default void afterMoved(int x, int y, Boolean onTop, Boolean onLeft) { }

        default void afterResized(int x, int y, int width, int height) {}
    }

}
